import {reactive, watchEffect} from 'vue'
import {createGlobalState} from "@vueuse/core";
import {empty, func} from "@/uni_modules/uv-ui-tools/libs/function/test";

function CacheResult(data, options) {
    this.data = data
    this.expires = options?.timeout ? (options.timeout + Date.now()) : null
    this.obsoleted = () => {
        return this.expires && this.expires < Date.now()
    }
}

export const useCacheStore = createGlobalState(() => {
    // data
    const container = reactive(new Map())
    const defaultOptions = {
        timeout: 2 * 3600 * 1000, // default cache 2 hours
        emptyIgnore: false // default cache empty values
    }

    // hooks
    watchEffect(() => {
        console.log('useCacheStore: cache container changed', container)
    })

    // functions
    const checkActionDefine = (action) => {
        if (!action) throw new Error('action is required')
        if (!action.type) throw new Error('action.type is required')
        if (!action.handler) throw new Error('action.handler is required')
        if (!func(action.handler)) throw new Error('action.handler must be a function')
    }
    const generateCacheKey = function (payload) {
        return JSON.stringify({payload})
    }
    const executeAction = async (action, payload) => {
        let result = action.handler(payload)
        if (result instanceof Promise) result = await result
        return result
    }

    /*
    * 获取缓存结果
    * @param {Object} action 缓存动作
    * @param {Object} payload 缓存参数
    * @param {Object} options 缓存选项
    * @returns {Object} 缓存结果
    * */
    const dispatchCache = async (action, payload, options = null) => {
        checkActionDefine(action)
        let typeNode = container.get(action.type)
        if (!typeNode) {
            typeNode = new Map()
            container.set(action.type, typeNode)
        }
        const key = generateCacheKey(payload)
        let cacheData = typeNode.get(key)

        // fire cached result
        if (cacheData instanceof CacheResult && !cacheData.obsoleted()) {
            return cacheData.data
        }

        // cache miss, execute action and cache result
        const result = await executeAction(action, payload)
        const cacheOptions = uni.$uv.deepMerge(defaultOptions, options)
        cacheData = new CacheResult(result, cacheOptions)
        if (!(empty(result) && cacheOptions.emptyIgnore)) {
            typeNode.set(key, cacheData)
        }
        return result
    }

    /*
    * 是否存在缓存
    * @param {Object} action 缓存动作
    * @param {Object} payload 缓存参数
    * @returns {Boolean} 是否存在缓存
    * */
    const hasCache = (action, payload) => {
        checkActionDefine(action)
        const typeNode = container.get(action.type)
        if (!typeNode) return false
        const key = generateCacheKey(payload)
        return typeNode.has(key)
    }

    /*
    * 删除缓存
    * @param {Object} action 缓存动作
    * @param {Object} payload 缓存参数
    * */
    const removeCache = (action, payload) => {
        checkActionDefine(action)
        const typeNode = container.get(action.type)
        if (!typeNode) return
        const key = generateCacheKey(payload)
        typeNode.delete(key)
        if (!typeNode.size) container.delete(action.type)
    }

    /*
    * 清理缓存
    * @param {Object} action 缓存动作，action = null时清空所有缓存
    * */
    const clearCache = (action = null) => {
        if (!action) {
            container.clear()
            return
        }
        checkActionDefine(action)
        container.delete(action.type)
    }

    /*
    * 清理过期缓存
    * */
    const gcCache = () => {
        if (!container.size) return
        console.log('run useCacheStore: gc')
        for (const [type, typeNode] of container.entries()) {
            for (const [key, cacheData] of typeNode.entries()) {
                if (cacheData.obsoleted()) {
                    typeNode.delete(key)
                    console.log('run useCacheStore: gc: remove cache', type, key)
                }
            }
            if (!typeNode.size) {
                container.delete(type)
                console.log('run useCacheStore: gc: remove type', type)
            }
        }
    }

    return {
        dispatchCache,
        hasCache,
        removeCache,
        clearCache,
        gcCache
    }
})
